<template>
    <div
            class="el-slider"
            :class="{ 'is-vertical': vertical, 'el-slider--with-input': showInput }"
            role="slider"
            :aria-valuemin="min"
            :aria-valuemax="max"
            :aria-orientation="vertical ? 'vertical': 'horizontal'"
            :aria-disabled="sliderDisabled"
    >
        <el-input-number
                v-model="firstValue"
                v-if="showInput && !range"
                class="el-slider__input"
                ref="input"
                @change="emitChange"
                :step="step"
                :disabled="sliderDisabled"
                :controls="showInputControls"
                :min="min"
                :max="max"
                :debounce="debounce"
                :size="inputSize">
        </el-input-number>
        <div
                class="el-slider__runway"
                :class="{ 'show-input': showInput, 'disabled': sliderDisabled }"
                :style="runwayStyle"
                @click="onSliderClick"
                ref="slider">
            <div
                    class="el-slider__bar"
                    :style="barStyle">
            </div>
            <slider-button
                    :vertical="vertical"
                    v-model="firstValue"
                    :tooltip-class="tooltipClass"
                    ref="button1">
            </slider-button>
            <slider-button
                    :vertical="vertical"
                    v-model="secondValue"
                    :tooltip-class="tooltipClass"
                    ref="button2"
                    v-if="range">
            </slider-button>
            <div
                    class="el-slider__stop"
                    v-for="(item, key) in stops"
                    :key="key"
                    :style="getStopStyle(item)"
                    v-if="showStops">
            </div>
            <template v-if="markList.length > 0">
                <div>
                    <div
                            v-for="(item, key) in markList"
                            :style="getStopStyle(item.position)"
                            class="el-slider__stop el-slider__marks-stop"
                            :key="key">
                    </div>
                </div>
                <div class="el-slider__marks">
                    <slider-marker
                            :mark="item.mark" v-for="(item, key) in markList"
                            :key="key"
                            :style="getStopStyle(item.position)">
                    </slider-marker>
                </div>
            </template>
        </div>
    </div>
</template>

<script type="text/babel">
    import ElInputNumber from 'element-ui/packages/input-number';
    import SliderButton from './button.vue';
    import SliderMarker from './marker';
    import Emitter from 'element-ui/src/mixins/emitter';

    export default {
        name: 'ElSlider',

        mixins: [Emitter],

        inject: {
            elForm: {
                default: ''
            }
        },

        props: {
            min: {
                type: Number,
                default: 0
            },
            max: {
                type: Number,
                default: 100
            },
            step: {
                type: Number,
                default: 1
            },
            value: {
                type: [Number, Array],
                default: 0
            },
            showInput: {
                type: Boolean,
                default: false
            },
            showInputControls: {
                type: Boolean,
                default: true
            },
            inputSize: {
                type: String,
                default: 'small'
            },
            showStops: {
                type: Boolean,
                default: false
            },
            showTooltip: {
                type: Boolean,
                default: true
            },
            formatTooltip: Function,
            disabled: {
                type: Boolean,
                default: false
            },
            range: {
                type: Boolean,
                default: false
            },
            vertical: {
                type: Boolean,
                default: false
            },
            height: {
                type: String
            },
            debounce: {
                type: Number,
                default: 300
            },
            label: {
                type: String
            },
            tooltipClass: String,
            marks: Object
        },

        components: {
            ElInputNumber,
            SliderButton,
            SliderMarker
        },

        data() {
            return {
                firstValue: null,
                secondValue: null,
                oldValue: null,
                dragging: false,
                sliderSize: 1
            };
        },

        watch: {
            value(val, oldVal) {
                if (this.dragging ||
                    Array.isArray(val) &&
                    Array.isArray(oldVal) &&
                    val.every((item, index) => item === oldVal[index])) {
                    return;
                }
                this.setValues();
            },

            dragging(val) {
                if (!val) {
                    this.setValues();
                }
            },

            firstValue(val) {
                if (this.range) {
                    this.$emit('input', [this.minValue, this.maxValue]);
                } else {
                    this.$emit('input', val);
                }
            },

            secondValue() {
                if (this.range) {
                    this.$emit('input', [this.minValue, this.maxValue]);
                }
            },

            min() {
                this.setValues();
            },

            max() {
                this.setValues();
            }
        },

        methods: {
            valueChanged() {
                if (this.range) {
                    return ![this.minValue, this.maxValue]
                        .every((item, index) => item === this.oldValue[index]);
                } else {
                    return this.value !== this.oldValue;
                }
            },
            setValues() {
                if (this.min > this.max) {
                    console.error('[Element Error][Slider]min should not be greater than max.');
                    return;
                }
                const val = this.value;
                if (this.range && Array.isArray(val)) {
                    if (val[1] < this.min) {
                        this.$emit('input', [this.min, this.min]);
                    } else if (val[0] > this.max) {
                        this.$emit('input', [this.max, this.max]);
                    } else if (val[0] < this.min) {
                        this.$emit('input', [this.min, val[1]]);
                    } else if (val[1] > this.max) {
                        this.$emit('input', [val[0], this.max]);
                    } else {
                        this.firstValue = val[0];
                        this.secondValue = val[1];
                        if (this.valueChanged()) {
                            this.dispatch('ElFormItem', 'el.form.change', [this.minValue, this.maxValue]);
                            this.oldValue = val.slice();
                        }
                    }
                } else if (!this.range && typeof val === 'number' && !isNaN(val)) {
                    if (val < this.min) {
                        this.$emit('input', this.min);
                    } else if (val > this.max) {
                        this.$emit('input', this.max);
                    } else {
                        this.firstValue = val;
                        if (this.valueChanged()) {
                            this.dispatch('ElFormItem', 'el.form.change', val);
                            this.oldValue = val;
                        }
                    }
                }
            },

            setPosition(percent) {
                const targetValue = this.min + percent * (this.max - this.min) / 100;
                if (!this.range) {
                    this.$refs.button1.setPosition(percent);
                    return;
                }
                let button;
                if (Math.abs(this.minValue - targetValue) < Math.abs(this.maxValue - targetValue)) {
                    button = this.firstValue < this.secondValue ? 'button1' : 'button2';
                } else {
                    button = this.firstValue > this.secondValue ? 'button1' : 'button2';
                }
                this.$refs[button].setPosition(percent);
            },

            onSliderClick(event) {
                if (this.sliderDisabled || this.dragging) return;
                this.resetSize();
                if (this.vertical) {
                    const sliderOffsetBottom = this.$refs.slider.getBoundingClientRect().bottom;
                    this.setPosition((sliderOffsetBottom - event.clientY) / this.sliderSize * 100);
                } else {
                    const sliderOffsetLeft = this.$refs.slider.getBoundingClientRect().left;
                    this.setPosition((event.clientX - sliderOffsetLeft) / this.sliderSize * 100);
                }
                this.emitChange();
            },

            resetSize() {
                if (this.$refs.slider) {
                    this.sliderSize = this.$refs.slider[`client${ this.vertical ? 'Height' : 'Width' }`];
                }
            },

            emitChange() {
                this.$nextTick(() => {
                    this.$emit('change', this.range ? [this.minValue, this.maxValue] : this.value);
                });
            },

            getStopStyle(position) {
                return this.vertical ? {'bottom': position + '%'} : {'left': position + '%'};
            }
        },

        computed: {
            stops() {
                if (!this.showStops || this.min > this.max) return [];
                if (this.step === 0) {
                    process.env.NODE_ENV !== 'production' &&
                    console.warn('[Element Warn][Slider]step should not be 0.');
                    return [];
                }
                const stopCount = (this.max - this.min) / this.step;
                const stepWidth = 100 * this.step / (this.max - this.min);
                const result = [];
                for (let i = 1; i < stopCount; i++) {
                    result.push(i * stepWidth);
                }
                if (this.range) {
                    return result.filter(step => {
                        return step < 100 * (this.minValue - this.min) / (this.max - this.min) ||
                            step > 100 * (this.maxValue - this.min) / (this.max - this.min);
                    });
                } else {
                    return result.filter(step => step > 100 * (this.firstValue - this.min) / (this.max - this.min));
                }
            },

            markList() {
                if (!this.marks) {
                    return [];
                }

                const marksKeys = Object.keys(this.marks);
                return marksKeys.map(parseFloat)
                    .sort((a, b) => a - b)
                    .filter(point => point <= this.max && point >= this.min)
                    .map(point => ({
                        point,
                        position: (point - this.min) * 100 / (this.max - this.min),
                        mark: this.marks[point]
                    }));
            },

            minValue() {
                return Math.min(this.firstValue, this.secondValue);
            },

            maxValue() {
                return Math.max(this.firstValue, this.secondValue);
            },

            barSize() {
                return this.range
                    ? `${ 100 * (this.maxValue - this.minValue) / (this.max - this.min) }%`
                    : `${ 100 * (this.firstValue - this.min) / (this.max - this.min) }%`;
            },

            barStart() {
                return this.range
                    ? `${ 100 * (this.minValue - this.min) / (this.max - this.min) }%`
                    : '0%';
            },

            precision() {
                let precisions = [this.min, this.max, this.step].map(item => {
                    let decimal = ('' + item).split('.')[1];
                    return decimal ? decimal.length : 0;
                });
                return Math.max.apply(null, precisions);
            },

            runwayStyle() {
                return this.vertical ? {height: this.height} : {};
            },

            barStyle() {
                return this.vertical
                    ? {
                        height: this.barSize,
                        bottom: this.barStart
                    } : {
                        width: this.barSize,
                        left: this.barStart
                    };
            },

            sliderDisabled() {
                return this.disabled || (this.elForm || {}).disabled;
            }
        },

        mounted() {
            let valuetext;
            if (this.range) {
                if (Array.isArray(this.value)) {
                    this.firstValue = Math.max(this.min, this.value[0]);
                    this.secondValue = Math.min(this.max, this.value[1]);
                } else {
                    this.firstValue = this.min;
                    this.secondValue = this.max;
                }
                this.oldValue = [this.firstValue, this.secondValue];
                valuetext = `${this.firstValue}-${this.secondValue}`;
            } else {
                if (typeof this.value !== 'number' || isNaN(this.value)) {
                    this.firstValue = this.min;
                } else {
                    this.firstValue = Math.min(this.max, Math.max(this.min, this.value));
                }
                this.oldValue = this.firstValue;
                valuetext = this.firstValue;
            }
            this.$el.setAttribute('aria-valuetext', valuetext);

            // label screen reader
            this.$el.setAttribute('aria-label', this.label ? this.label : `slider between ${this.min} and ${this.max}`);

            this.resetSize();
            window.addEventListener('resize', this.resetSize);
        },

        beforeDestroy() {
            window.removeEventListener('resize', this.resetSize);
        }
    };
</script>
